import { reactive, inject } from 'vue'

const storeKey = 'store'

class StoreV1 {
    constructor(options) {
        const store = this
        store._state = reactive({data: options.state})
        registerGetters(store, options.getters)
        registerMutations(store, options.mutations)
        registerActions(store, options.actions)
        this.commit = this.commit.bind(this)
        this.dispatch = this.dispatch.bind(this)
    }

    get state() {
        return this._state.data
    }

    get getters() {
        return this._getters
    }

    commit (type, payload) {
        const mutation = this._mutations[type];
        if (mutation) {
            mutation(this.state, payload);
        }
    }

    dispatch (type, payload) {
        const action = this._actions[type];
        if (action) {
            return action(this, payload);
        }
    }

    install(app, injectKey = storeKey) {
        app.provide(injectKey, this)
        app.config.globalProperties.$store = this
    }
}

function registerGetters(store, getters) {
    if (!getters) return;

    store._getters = {}
    forEachValue(getters, (getter, key) => {
        Object.defineProperty(store._getters, key, {
            get: () => getter(store.state),
        });
    });
}

function registerMutations(store, mutations) {
    if (!mutations) return;

    store._mutations = Object.create(null)
    forEachValue(mutations, (mutation, key) => {
        store._mutations[key] = (state, payload) => mutation(state, payload);
    });
}

function registerActions(store, actions) {
    if (!actions) return;

    store._actions = Object.create(null)
    forEachValue(actions, (action, key) => {
        store._actions[key] = (store, payload) => action(store, payload);
    });
}

function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function createStore(options) {
    return new StoreV1(options)
}

function useStore(injectKey = storeKey) {
    const store = inject(injectKey);
    if (!store) {
        throw new Error('No store provided.');
    }
    return store;
}

export {
    createStore,
    useStore
}
